/*******************************************************************************
 * Owlets Framework
 * Copyright 2011 Logicfish Software http://logicfishsoftware.com/
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 * 
 * @author logicfish@gmail.com
 *
 ******************************************************************************/

package com.logicfishsoftware.owlet.app.runtime;

import com.logicfishsoftware.owlet.app.Application;
import com.logicfishsoftware.owlet.app.ApplicationException;
import com.logicfishsoftware.owlet.app.ApplyTo;
import com.logicfishsoftware.owlet.app.Operator;

/**
 * <p>A class to facilitate atomic operations, forming a wrapper 
 * to another class or library where the atomic operations will occur, such as a JDBC style connection for example.</p>
 * Providers will implement this class to provide atomic access to their underlying library.
 * <p>
 * The interface {@link ApplicationAtom} is to be implemented by clients of the runtime loaders.
 * They override the
 * {@link ApplicationAtom#application(ApplicationContext)} method to implement their operation; the loader class ensures all 
 * resources are disposed after the method returns.
 * </p><p>
 * The {@link ApplicationContext} interface is implemented by providers, and should provide access to
 * the underlying library.
 * </p><p>
 * Both {@link Atom} and {@link ApplicationContext} classes should be overridden by implementors.  They are used as type 
 * parameters when extending the {@link RuntimeApplication} class.  This is to ensure that
 * the {@link #enter(ApplicationAtom)} method of the loader can only be called using the correct type,
 * without the need for clients to cast the ApplicationContext value to the correct type.  It also
 * allows the correct types to be used in the overridden methods which create and 
 * dispose contexts. 
 * </p>
 * Example:<br/>
 * {@code class MyClass extends RuntimeApplication<MyAtomic,MyContext>}  
 * <p>
 * <strong>NOTE:</strong> 
 * </p>
 * @see TransactionalRuntime 
 * @see RuntimeService
 * 
 * @author logicfish@gmail.com
 *
 */
public abstract class RuntimeApplication<T extends RuntimeApplication<T,U>.ApplicationAtom,U extends RuntimeApplication<T,U>.ApplicationContext> 
//implements ApplyTo<Application<T>,T> 
implements ApplyTo 
{
	/**
	 * <p>This type should be overridden by implementors of {@link RuntimeApplication}.
	 * They should add methods here to provide bare-bones access to their libraries.</p>
	 * <p>Instances of the derived class will then be used by {@link ApplicationAtom}s during their operation phase.
	 * 
	 * @author logicfish@gmail.com
	 *
	 */
	public abstract class ApplicationContext {
		/**
		 * Implementors should override this method to dispose {@link ApplicationContext} objects 
		 * created by the {@link RuntimeApplication#createAppContext(ApplicationAtom)} method.
		 * @param context 
		 * 
		 */
		protected abstract void dispose() throws ApplicationException;
	}
	
	/**
	 * This type is to be overridden by clients, to encapsulate an instance of an operation
	 * to be performed.  The {@link ApplicationAtom#application(ApplicationContext)} method will be invoked by the loader during the
	 * {@link RuntimeApplication#enter(ApplicationAtom)} method.  Generally,
	 * libraries will provide a delegate class such as an operator rather than have clients use this class
	 * directly.
	 * 
	 * @author logicfish@gmail.com
	 *
	 */
	public abstract class ApplicationAtom {
		/**
		 * This method should be overridden by clients to execute their workflow, 
		 * and will be called during the {@link RuntimeApplication#enter(ApplicationAtom)} method.
		 * @param context
		 * @return {@code true} if {@link #proceed()} should be executed. 
		 * @throws Exception Any exception thrown by this method will be wrapped in
		 * an {@link ApplicationException}, thrown during the {@link RuntimeApplication#enter(ApplicationAtom)} method. 
		 */
		protected abstract boolean application(U context) throws Exception;
		
		/**
		 * This method is to be overridden by clients and is called during the {@link RuntimeApplication#enter(ApplicationAtom)}
		 * method, after the context has been disposed, if the client returned {@code true} from the
		 * application phase.   This allows us to tail-gate another atomic operation whilst maintaining scalability -
		 * all the resources used by our connection have been released by the {@link ApplicationContext#dispose()} method.
		 * 
		 * @return 
		 * @throws ApplicationException 
		 */
		 protected abstract T proceed() throws ApplicationException;
	}
	
	/**
	 * Implementors should override this method to create {@link ApplicationContext} objects for
	 * clients.   
	 * 
	 * @param atomic 
	 * @return 
	 */
	protected abstract U createAppContext(T atomic);
	/**
	 * Creates an instance of the application of an {@link Operator}.
	 * 
	 * @param operator
	 * @return
	 * @throws ApplicationException
	 */
	protected abstract T createAtom(Operator<? super U> operator) throws ApplicationException;
	
	/**
	 * Enters the application.
	 *   
	 * @param atomic
	 * @return 
	 * @throws ApplicationException
	 */
	protected boolean enter(T atomic) throws ApplicationException {
		U context = this.createAppContext(atomic);
		return apply(context,atomic);
	}
	
	/**
	 * Default behaviour for the {@link #apply(ApplicationContext, ApplicationAtom)} method.
	 * Override to change this behaviour, for example to add support for transactions.
	 * The context parameter allows implementors to provide their own instance of 
	 * the context class, in order to provide specialisations of the class in various 
	 * situations.  Instances of the context class however should not be reused 
	 * in more than one call to the {@link #apply(ApplicationContext, ApplicationAtom)} method.
	 * Context classes should be designed to free all their resources during the
	 * {@link ApplicationContext#dispose()} method.
	 * 
	 * TODO the method is too complex - find a simpler way using two or more ultra-simple methods.
	 * ie: invoke(T).proceed() throws TerminateAppExeption; // (throws the exeption when the proceed object is null 
	 * instead of firing an NPE.
	 * 
	 * @param context
	 * @param atomic
	 * @throws ApplicationException
	 */
	protected boolean apply(U context, T atomic) throws ApplicationException {
		boolean proceed = false;
		try {
			proceed = atomic.application(context);  
		} catch (Throwable throwable) {
			throw new ApplicationException(throwable);
		} finally {
			context.dispose();
		}
		context = null;
		if(proceed) {
			T tail = atomic.proceed();
			atomic = null;
			if(tail!=null) 
				enter(tail);
		}
		return proceed;
	}
	
	public Application<T> getApplication() {
		return new Application<T>(){
			public boolean enter(T atomic) throws ApplicationException {
				return RuntimeApplication.this.enter(atomic);
			}};
	}

	@SuppressWarnings("unchecked")
	public <K> Application<K> apply(Class<K> cla) {
		// Should be <K extends T> but there's not way to implement this.
		return (Application<K>) this;
	}

}
